import json
import os
import sys
import time

from flask import Flask, render_template
from flask import request

from action import Action
# from block_chain import BlockChain
# from cnrp import LocalCNRP
from data_bundle import DataBundle
# from node_file import NodeFile

from cnrp_experiment.exp_test import run_exp_for_web


web_app = Flask(__name__)
action = Action()


# 2021.10.25: 对所有接口的返回格式做了修改
# 直接 return [dict] 则调用flask中的jsonify(); 修改后为调用json中的dumps()


# 将所有接口分为两类：1. 定时刷新的接口；2. 不定时请求的接口。
# get_x 系接口为不需要参数的就可以获取数据
# ask_x 系接口为需要提交参数才能获取到数据

# 统一JSON传输格式：{state: , error: , content: ,}
# state:  0: 是 正常
#        -1: 否 错误
# error: <str> 对state的附加具体描述
# content: 传输内容 通常为 dict (json)


@web_app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('index.html')


@web_app.route('/get_height', methods=['GET'])
def get_height():
    # bc = BlockChain()
    state, height = action.get_cur_height()
    return json.dumps({'state': state, 'error': '', 'content': {'height': height}})


@web_app.route('/get_state', methods=['GET'])
def get_state():
    # bc = BlockChain()
    last_block = action.get_last_block_of_chain()[1]

    response_dict = {
        'state': 0,
        'error': '',
        'content': {
            'num_node': len(last_block.block_header.cnrp.get('cnrp_local', {})),
            'height': last_block.block_header.height,
            'block': get_block_dict(last_block)
        }
    }
    return json.dumps(response_dict)


@web_app.route('/get_local_node', methods=['GET'])
def get_local_node():
    # action = Action()
    state, local_node_dict = action.get_local_node_info()
    if state != 0:
        return json.dumps({'state': -1, 'error': local_node_dict})
    else:
        return json.dumps({'state': 0, 'error': '', 'content': local_node_dict})


@web_app.route('/get_create_node', methods=['GET'])
def get_create_node():
    # action = Action()
    state, local_node_address = action.create_local_node()
    if state != 0:
        return json.dumps({'state': -1})
    else:
        return json.dumps({'state': 0, 'error': '', 'content': local_node_address})


@web_app.route('/get_delete_node', methods=['GET'])
def get_delete_node():
    # action = Action()
    state, info = action.delete_local_node()
    if state != 0:
        return json.dumps({'state': -1})
    else:
        return json.dumps({'state': 0})


@web_app.route('/get_local_cnrp', methods=['GET'])
def get_local_cnrp():
    state, local_cnrp = action.get_local_cnrp_dict()
    if state == -1:
        return json.dumps({'state': state, 'error': 'no local node', 'content': ''})
    return json.dumps({'state': 0, 'error': '', 'content': local_cnrp})


@web_app.route('/ask_chain', methods=['POST'])
def ask_chain():
    RESPONSE_BLOCK_NUM = 5

    # bc = BlockChain()
    response_blocks = {}

    if request.json.get('range') == '0,0':
        height = action.get_last_block_of_chain()[1].block_header.height
        i = 1
        for h in range(height, 0, -1):
            if i > RESPONSE_BLOCK_NUM:
                break
            response_blocks[str(i)] = get_block_dict(action.get_block_by_height(h)[1])
            i += 1

    else:
        rqt_mode = request.json.get('mode')
        rqt_range = [int(_) for _ in request.json.get('range').split(',')]

        # mode 0 为前向更新 即更新最新block
        if rqt_mode == 0:
            height = action.get_last_block_of_chain()[1].block_header.height
            web_height = rqt_range[1]
            i = 1
            for h in range(web_height+1, height+1):
                if i > RESPONSE_BLOCK_NUM:
                    break
                response_blocks[str(i)] = get_block_dict(action.get_block_by_height(h)[1])
                i += 1
        # mode 1 为后向更新 获取历史block
        elif rqt_mode == 1:
            web_height = rqt_range[0]
            i = 1
            for h in range(web_height-1, 0, -1):
                if i > RESPONSE_BLOCK_NUM:
                    break
                response_blocks[str(i)] = get_block_dict(action.get_block_by_height(h)[1])
                i += 1
    if response_blocks == {}:
        return json.dumps({'state': -1, 'error': 'none to update', 'content': response_blocks})
    return json.dumps({'state': 0, 'error': '', 'content': response_blocks})


@web_app.route('/ask_query', methods=['POST'])
def ask_query():
    # action = Action()
    find_condition = {}
    for key, value in request.json.get('find_condition').items():
        if value != '':
            find_condition[key] = value
    if 'height' in find_condition:
        find_condition['height'] = int(find_condition['height'])
    # print(find_condition)
    block_list = action.find_blocks(find_condition)

    if request.json.get('where') == 'release_assist':
        if find_condition['tx_type'] == 'share':
            for i in range(len(block_list)-1, -1, -1):
                business_list = action.bc.find_block_by_business_id(block_list[i].transactions.business_id)
                if len(business_list) > 2:
                    del block_list[i]
        elif find_condition['tx_type'] == 'evaluate':
            for i in range(len(block_list)-1, -1, -1):
                business_list = action.bc.find_block_by_business_id(block_list[i].transactions.business_id)
                if len(business_list) > 3:
                    del block_list[i]
        else:
            pass

    if request.json.get('block_info_type') == 0:
        block_list = [get_block_dict(b) for b in block_list]
    elif request.json.get('block_info_type') == 1:
        block_list = [get_block_dict(b, 1) for b in block_list]
    else:
        return json.dumps({'state': -1, 'error': 'wrong field'})
    return json.dumps({'state': 0, 'error': '', 'content': block_list})


@web_app.route('/ask_type_num', methods=['POST'])
def ask_type_num():
    # action = Action()
    xtype = request.json.get('xtype')

    state, num = action.get_xtype_num(xtype)
    if state == 0:
        return json.dumps({'state': 0, 'error': '', 'content': num})
    else:
        return json.dumps({'state': -1, 'error': ''})


@web_app.route('/ask_upload', methods=['POST'])
def ask_upload():
    # action = Action()
    upload_data = request.json.get('upload_data')
    upload_desc = request.json.get('upload_desc')

    if upload_data == '' or upload_desc == '':
        return json.dumps({'state': -1, 'error': 'incomplete filling', 'content': ''})

    state, tx_id = action.upload(upload_data, upload_desc)
    return json.dumps({'state': state, 'error': '', 'content': tx_id})


@web_app.route('/ask_request', methods=['POST'])
def ask_request():
    # action = Action()
    request_data_id = request.json.get('data_id')
    state, tx_id = action.request(request_data_id)
    return json.dumps({'state': state, 'error': '', 'content': tx_id})


@web_app.route('/ask_share', methods=['POST'])
def ask_share():
    # action = Action()
    prev_tx_id = request.json.get('tx_id')
    state, tx_id = action.share(prev_tx_id)
    return json.dumps({'state': state, 'error': '', 'content': tx_id})


@web_app.route('/ask_evaluate', methods=['POST'])
def ask_evaluate():
    # action = Action()
    prev_tx_id = request.json.get('tx_id')
    data_score = int(request.json.get('data_score'))
    data_score = round((data_score-50)/5, 2)
    state, tx_id = action.evaluate(prev_tx_id, data_score)
    return json.dumps({'state': state, 'error': '', 'content': tx_id})


@web_app.route('/ask_unpack', methods=['POST'])
def ask_unpack():
    # action = Action()
    share_tx_id = request.json.get('tx_id')
    state, data_bundle = action.unpack_data(share_tx_id)
    if state == 0:
        return json.dumps({'state': state, 'error': '', 'content': data_bundle.serialize()})
    else:
        return json.dumps({'state': state, 'error': data_bundle})


@web_app.route('/ask_global_cnrp', methods=['POST'])
def ask_global_cnrp():
    # action = Action()
    res_cnrp_list = []
    res_height_list = []
    height_list = request.json.get('height_list')
    last_height = action.get_cur_height()[1]
    for height in height_list:
        if not isinstance(height, int):
            height = int(height)
        if height < 0:
            height = last_height + height + 1
        if 0 < height <= last_height:
            state, cnrp_dict = action.get_global_cnrp_by_height(height)
            if state == 0:
                res_cnrp_list.append(cnrp_dict)
                res_height_list.append(height)
            else:
                return json.dumps({'state': state, 'error': ''})
    return json.dumps({'state': 0, 'error': '', 'content': {'height': res_height_list, 'cnrp': res_cnrp_list}})


# ----------------------------------------------------------------------


# 对给定的 block 返回 block dict
# 可选两张类型 -0/1 简版/完整版 默认简版
def get_block_dict(block, dict_type=0):
    _tx_content = block.transactions.tx_content

    def _pack_upload():
        _tx_content.data = DataBundle.deserialize(_tx_content.data)
        return {'data': {'data_id': _tx_content.data.data_id, 'data_source': _tx_content.data.data_source,
                'data_time': _tx_content.data.data_time, 'data_describe': _tx_content.data.data_describe,
                'data_content': _tx_content.data.data_content, 'is_lock': _tx_content.data.is_lock}}

    def _pack_request():
        return {'source_rsa_public_key': _tx_content.source_rsa_public_key}

    def _pack_share():
        return {'key_ciphertext': _tx_content.key_ciphertext}

    def _pack_evaluate():
        return {'data_evaluate': _tx_content.data_evaluate, 'data_source_node_evaluate': _tx_content.data_source_node_evaluate}

    _selector = {'upload': _pack_upload, 'request': _pack_request, 'share': _pack_share, 'evaluate': _pack_evaluate}

    # 简版 dict
    if dict_type == 0:

        block_dict = {
            'height': block.block_header.height,
            'block_hash': block.block_header.block_hash,
            'prev_block_hash': block.block_header.prev_block_hash,
            'cnrp': block.block_header.cnrp.get('cnrp_value', {}),
            'tx_id': block.transactions.tx_id,
            'previous_tx_id': block.transactions.previous_tx_id,
            'tx_type': block.transactions.tx_type,
            'tx_time': block.transactions.tx_time,
            'data_id': block.transactions.data_id,
            'business_id': block.transactions.business_id,
            'node_address': block.transactions.tx_content.public_key_hash
        }

    # 完整版
    elif dict_type == 1:

        block_dict = {
            'timestamp': block.block_header.timestamp,
            'height': block.block_header.height,
            'nonce': block.block_header.nonce,
            'block_hash': block.block_header.block_hash,
            'prev_block_hash': block.block_header.prev_block_hash,
            'hash_merkle_root': block.block_header.hash_merkle_root,
            'cnrp': block.block_header.cnrp.get('cnrp_value', {}),
            'tx_id': block.transactions.tx_id,
            'previous_tx_id': block.transactions.previous_tx_id,
            'tx_type': block.transactions.tx_type,
            'tx_time': block.transactions.tx_time,
            'source_node_address': block.transactions.tx_content.source_node_address,
            'node_address': block.transactions.tx_content.public_key_hash,
            'data_id': block.transactions.data_id,
            'business_id': block.transactions.business_id,
            'signature': block.transactions.signature
        }
    else:
        return
    block_dict.update(_selector[_tx_content.tx_type]())
    return block_dict


# ----------- 本地数据库接口 -----------------

@web_app.route('/db/', methods=['GET', 'POST'])
def db():
    return render_template('db.html')


@web_app.route('/db/db_get_data/', methods=['GET'])
def db_get_data():
    state, db_data = action.db_show()
    if state != 0:
        return json.dumps({'state': -1, 'error': ''})
    else:
        return json.dumps({'state': 0, 'error': '', 'content': db_data})


@web_app.route('/db/db_clean_up/', methods=['GET'])
def db_clean_up():
    state = action.db_clean_up()
    if state != 0:
        return json.dumps({'state': -1, 'error': ''})
    else:
        return json.dumps({'state': 0, 'error': '', 'content': ''})


# ----------- 实验视图接口 -------------------

# 全局变量 数据容器
Global_data_container = {}


@web_app.route('/exp/', methods=['GET', 'POST'])
def exp():
    return render_template('experiment.html')


@web_app.route('/exp/exp_get_data', methods=['GET'])
def exp_get_data():
    global Global_data_container
    while Global_data_container == {}:
        time.sleep(1000)

    # Global_data_containerget_cnrp_value_package(GlobalDict.chain_dict)
    # package <dict>: {
    #     node_id <int>: rp -cnrp.value <float>,
    #     ...
    # }
    package = {}
    for h, block in Global_data_container.chain_dict.items():
        package[h] = block.block_header.cnrp.value

    # data_package <dict>: {
    #     'height' <list>: [block height <int>, ...],
    #     'value' <dict>: {
    #         node 1 <int>: node 1 rp by height <list>,
    #         node 2 <int>: node 2 rp by height <list>,
    #         node 3 <int>: node 3 rp by height <list>,
    #         ...
    #     }
    # }
    data_package = {'height': [], 'value': {}}

    for height, cnrp in package.items():
        for node, value in cnrp.items():
            rp_list = data_package['value'].get(node, [])
            rp_list.append(value)
            data_package['value'][node] = rp_list

        data_package['height'].append(height)

    for node, rp_list in data_package['value'].items():
        if len(rp_list) < data_package['height'][-1]:
            for _ in range(data_package['height'][-1]-len(rp_list)+1):
                data_package['value'][node].insert(0, 0)

    return json.dumps({'state': 0, 'error': '', 'content': data_package})


@web_app.route('/exp/exp_run_exp', methods=['GET'])
def exp_run_exp():
    global Global_data_container
    Global_data_container = {}
    Global_data_container = run_exp_for_web()
    return json.dumps({'state': 0, 'error': '', 'content': ''})


if __name__ == '__main__':
    web_app.run()

